/*
 * Copyright (c) 2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.GeneticOptimizer;

import java.lang.reflect.Field;
import java.util.Arrays;

import org.apache.commons.math.genetics.Chromosome;


import yawning.mew.Report;
import yawning.mew.GeneticOptimizer.Optimizer.CandidateStat;
import yawning.mew.character.Toon;

public class GOChromosome extends Chromosome {
	private Optimizer mOptimizer;
	int mStatDeltas[];

	// Store readable values for all of these for sanity.
	int mSpirit;
	int mDodgeRating;
	int mParryRating;
	int mHasteRating;
	int mCritRating;
	int mMasteryRating;
	int mExpertiseRating;
	int mHitRating;

	public GOChromosome(Optimizer opt) {
		mOptimizer = opt;
		mStatDeltas = new int[mOptimizer.mNrStats];
	}

	public GOChromosome(Optimizer opt, int statDeltas[]) {
		mOptimizer = opt;
		mStatDeltas = statDeltas;

		int foo = 0;
		for (int i = 0; i < statDeltas.length; i++) {
			foo += statDeltas[i];
		}
		if (foo != mOptimizer.mStatPool) {
			System.out.println("Expected " + mOptimizer.mStatPool + " Have: " + foo);
		}

		updateCachedValues();
	}

	public GOChromosome newRandomChromosome() {
		GOChromosome chromosome = new GOChromosome(this.mOptimizer);

		// Create a new Chromosome with a random stat spread.
		int nrStats = mOptimizer.mNrStats;
		int statPool = mOptimizer.mStatPool;

		// Convert the representation into stat values.
		double[] u = new double[nrStats + 1];
		for (int i = 1; i < nrStats; i++) {
			u[i] = mOptimizer.mRng.nextDouble();
		}
		u[0] = 0.0; u[nrStats] = 1.0;
		Arrays.sort(u);
		int allocatedStats = 0;
		for (int i = 0; i < nrStats; i++) {
			int alloc = (int) Math.round(statPool * (u[i + 1] - u[i]));
			allocatedStats += alloc;
			chromosome.mStatDeltas[i] = alloc;
		}
		chromosome.mStatDeltas[0] += statPool - allocatedStats;

		chromosome.updateCachedValues();

		return chromosome;
	}

	@Override
	public double fitness() {
		Toon toon = mOptimizer.mToonToonBuilder.build(mOptimizer.mBaseToon);
		for (int i = 0; i < mOptimizer.mNrStats; i++) {
			CandidateStat stat = mOptimizer.mCandidateStats.get(i);
			Field f = mOptimizer.mStatToonFieldMap.get(stat);
			try {
				int value = f.getInt(toon);
				f.setInt(toon, value + mStatDeltas[i]);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

		Report result = mOptimizer.mMew.compute(toon, mOptimizer.mTarget, mOptimizer.mConfig);
		return result.getResult(mOptimizer.mFitnessStat).getMean();
	}

	@Override
	protected boolean isSame(Chromosome anoth) {
		GOChromosome another = (GOChromosome) anoth;
		return Arrays.equals(mStatDeltas, another.mStatDeltas);
	}

	private void updateCachedValues() {
		for (int i = 0; i < mOptimizer.mNrStats; i++) {
			CandidateStat stat = mOptimizer.mCandidateStats.get(i);
			Field f = mOptimizer.mStatToonFieldMap.get(stat);
			try {
				int value = f.getInt(mOptimizer.mBaseToon);
				switch (stat) {
				case SPIRIT: mSpirit = value + mStatDeltas[i]; break;
				case DODGE_RATING: mDodgeRating = value + mStatDeltas[i]; break;
				case PARRY_RATING: mParryRating = value + mStatDeltas[i]; break;
				case HASTE_RATING: mHasteRating = value + mStatDeltas[i]; break;
				case CRIT_RATING: mCritRating = value + mStatDeltas[i]; break;
				case MASTERY_RATING: mMasteryRating = value + mStatDeltas[i]; break;
				case EXPERTISE_RATING: mExpertiseRating = value + mStatDeltas[i]; break;
				case HIT_RATING: mHitRating = value + mStatDeltas[i]; break;
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

		if (mOptimizer.mPopulation != null) searchForFitnessUpdate(mOptimizer.mPopulation);
	}

	@Override
	public String toString() {
		StringBuilder s = new StringBuilder();
		s.append("Fitness: ").append(Report.round(getFitness())).append("  ");
		for (int i = 0; i < mOptimizer.mNrStats; i++) {
			CandidateStat stat = mOptimizer.mCandidateStats.get(i);
			switch (stat) {
			case SPIRIT: s.append("Spirit: " + mSpirit + " (" + mStatDeltas[i] + ")").append(' '); break;
			case DODGE_RATING: s.append("Dodge Rating: " + mDodgeRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			case PARRY_RATING: s.append("Parry Rating: " + mParryRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			case HASTE_RATING: s.append("Haste Rating: " + mHasteRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			case CRIT_RATING: s.append("Crit Rating: " + mCritRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			case HIT_RATING: s.append("Hit Rating: " + mHitRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			case EXPERTISE_RATING: s.append("Expertise Rating: " + mExpertiseRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			case MASTERY_RATING: s.append("Mastery Rating: " + mMasteryRating + " (" + mStatDeltas[i] + ")").append(' '); break;
			}

		}

		return s.toString();
	}
}
